package core;

import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferInt;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import java.util.HashMap;
import java.util.Map;
import java.awt.GraphicsEnvironment;

import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.util.glu.GLU;


/**
 * A TrueType font implementation originally for Slick, edited for Bobjob's Engine
 * 
 * @original author James Chambers (Jimmy)
 * @original author Jeremy Adams (elias4444)
 * @original author Kevin Glass (kevglass)
 * @original author Peter Korzuszek (genail)
 * 
 * @new version edited by David Aaron Muhar (bobjob)
 */
public class GLFont {
   public final static int
      ALIGN_LEFT = 0,
      ALIGN_RIGHT = 1,
      ALIGN_CENTER = 2;
   /** Array that holds necessary information about the font characters */
   private IntObject[] charArray = new IntObject[256];
   
   /** Map of user defined font characters (Character <-> IntObject) */
   private Map customChars = new HashMap();

   /** Boolean flag on whether AntiAliasing is enabled or not */
   private boolean antiAlias;

   /** Font's size */
   private int fontSize = 0;

   /** Font's height */
   private int fontHeight = 0;

   /** Texture used to cache the font 0-255 characters */
   private int fontTextureID;
   
   /** Default font texture width */
   private int textureWidth = 512;

   /** Default font texture height */
   private int textureHeight = 512;

   /** A reference to Java's AWT Font that we create our font texture from */
   private Font font;

   /** The font metrics for our Java AWT font */
   private FontMetrics fontMetrics;

   
   private int correctL = 9, correctR = 8;
   
   private class IntObject {
      /** Character's width */
      public int width;

      /** Character's height */
      public int height;

      /** Character's stored x position */
      public int storedX;

      /** Character's stored y position */
      public int storedY;
   }


   public GLFont(Font font, boolean antiAlias, char[] additionalChars) {
      this.font = font;
      this.fontSize = font.getSize()+3;
      this.antiAlias = antiAlias;

      createSet( additionalChars );
      
      fontHeight -= 1;
      if (fontHeight <= 0) fontHeight = 1;
   }

   public GLFont(Font font, boolean antiAlias) {
      this( font, antiAlias, null );
   }
   public void setCorrection(boolean on) {
      if (on) {
         correctL = 2;
         correctR = 1;
      } else {
         correctL = 0;
         correctR = 0;
      }
   }
   private BufferedImage getFontImage(char ch) {
      // Create a temporary image to extract the character's size
      BufferedImage tempfontImage = new BufferedImage(1, 1,
            BufferedImage.TYPE_INT_ARGB);
      Graphics2D g = (Graphics2D) tempfontImage.getGraphics();
      if (antiAlias == true) {
         g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
               RenderingHints.VALUE_ANTIALIAS_ON);
      }
      g.setFont(font);
      fontMetrics = g.getFontMetrics();
      int charwidth = fontMetrics.charWidth(ch)+8;

      if (charwidth <= 0) {
         charwidth = 7;
      }
      int charheight = fontMetrics.getHeight()+3;
      if (charheight <= 0) {
         charheight = fontSize;
      }

      // Create another image holding the character we are creating
      BufferedImage fontImage;
      fontImage = new BufferedImage(charwidth, charheight,
            BufferedImage.TYPE_INT_ARGB);
      Graphics2D gt = (Graphics2D) fontImage.getGraphics();
      if (antiAlias == true) {
         gt.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
               RenderingHints.VALUE_ANTIALIAS_ON);
      }
      gt.setFont(font);

      gt.setColor(Color.WHITE);
      int charx = 3;
      int chary = 1;
      gt.drawString(String.valueOf(ch), (charx), (chary)
            + fontMetrics.getAscent());

      return fontImage;

   }

   private void createSet( char[] customCharsArray ) {
      // If there are custom chars then I expand the font texture twice    
      if (customCharsArray != null && customCharsArray.length > 0) {
         textureWidth *= 2;
      }
      
      // In any case this should be done in other way. Texture with size 512x512
      // can maintain only 256 characters with resolution of 32x32. The texture
      // size should be calculated dynamicaly by looking at character sizes. 
      
      try {
         
         BufferedImage imgTemp = new BufferedImage(textureWidth, textureHeight, BufferedImage.TYPE_INT_ARGB);
         Graphics2D g = (Graphics2D) imgTemp.getGraphics();

         g.setColor(new Color(0,0,0,1));
         g.fillRect(0,0,textureWidth,textureHeight);
         
         int rowHeight = 0;
         int positionX = 0;
         int positionY = 0;
         
         int customCharsLength = ( customCharsArray != null ) ? customCharsArray.length : 0; 

         for (int i = 0; i < 256 + customCharsLength; i++) {
            
            // get 0-255 characters and then custom characters
            char ch = ( i < 256 ) ? (char) i : customCharsArray[i-256];
            
            BufferedImage fontImage = getFontImage(ch);

            IntObject newIntObject = new IntObject();

            newIntObject.width = fontImage.getWidth();
            newIntObject.height = fontImage.getHeight();

            if (positionX + newIntObject.width >= textureWidth) {
               positionX = 0;
               positionY += rowHeight;
               rowHeight = 0;
            }

            newIntObject.storedX = positionX;
            newIntObject.storedY = positionY;

            if (newIntObject.height > fontHeight) {
               fontHeight = newIntObject.height;
            }

            if (newIntObject.height > rowHeight) {
               rowHeight = newIntObject.height;
            }

            // Draw it here
            g.drawImage(fontImage, positionX, positionY, null);

            positionX += newIntObject.width;

            if( i < 256 ) { // standard characters
               charArray[i] = newIntObject;
            } else { // custom characters
               customChars.put( new Character( ch ), newIntObject );
            }

            fontImage = null;
         }

         fontTextureID = loadImage(imgTemp);
         


               //.getTexture(font.toString(), imgTemp);

      } catch (Exception e) {
         System.err.println("Failed to create font.");
         e.printStackTrace();
      }
   }
   
   private void drawQuad(float drawX, float drawY, float drawX2, float drawY2,
         float srcX, float srcY, float srcX2, float srcY2) {
      float DrawWidth = drawX2 - drawX;
      float DrawHeight = drawY2 - drawY;
      float TextureSrcX = srcX / textureWidth;
      float TextureSrcY = srcY / textureHeight;
      float SrcWidth = srcX2 - srcX;
      float SrcHeight = srcY2 - srcY;
      float RenderWidth = (SrcWidth / textureWidth);
      float RenderHeight = (SrcHeight / textureHeight);

      GL11.glTexCoord2f(TextureSrcX, TextureSrcY);
      GL11.glVertex2f(drawX, drawY);
      GL11.glTexCoord2f(TextureSrcX, TextureSrcY + RenderHeight);
      GL11.glVertex2f(drawX, drawY + DrawHeight);
      GL11.glTexCoord2f(TextureSrcX + RenderWidth, TextureSrcY + RenderHeight);
      GL11.glVertex2f(drawX + DrawWidth, drawY + DrawHeight);
      GL11.glTexCoord2f(TextureSrcX + RenderWidth, TextureSrcY);
      GL11.glVertex2f(drawX + DrawWidth, drawY);
   }

   public int getWidth(String whatchars) {
      int totalwidth = 0;
      IntObject intObject = null;
      int currentChar = 0;
      for (int i = 0; i < whatchars.length(); i++) {
         currentChar = whatchars.charAt(i);
         if (currentChar < 256) {
            intObject = charArray[currentChar];
         } else {
            intObject = (IntObject)customChars.get( new Character( (char) currentChar ) );
         }
         
         if( intObject != null )
            totalwidth += intObject.width;
      }
      return totalwidth;
   }

   public int getHeight() {
      return fontHeight;
   }


   public int getHeight(String HeightString) {
      return fontHeight;
   }

   public int getLineHeight() {
      return fontHeight;
   }

   public void drawString(float x, float y,
         String whatchars, float scaleX, float scaleY) {
      drawString(x,y,whatchars, 0, whatchars.length()-1, scaleX, scaleY, ALIGN_LEFT);
   }
   public void drawString(float x, float y,
         String whatchars, float scaleX, float scaleY, int format) {
      drawString(x,y,whatchars, 0, whatchars.length()-1, scaleX, scaleY, format);
   }


   public void drawString(float x, float y,
         String whatchars, int startIndex, int endIndex,
         float scaleX, float scaleY,
         int format
         ) {
      
      IntObject intObject = null;
      int charCurrent;
      

      int totalwidth = 0;
      int i = startIndex, d, c;
      float startY = 0;


      
      switch (format) {
         case ALIGN_RIGHT: {
            d = -1;
            c = correctR;
         
            while (i < endIndex) {
               if (whatchars.charAt(i) == '\n') startY -= fontHeight;
               i++;
            }
            break;
         }
         case ALIGN_CENTER: {
            for (int l = startIndex; l <= endIndex; l++) {
               charCurrent = whatchars.charAt(l);
               if (charCurrent == '\n') break;
               if (charCurrent < 256) {
                  intObject = charArray[charCurrent];
               } else {
                  intObject = (IntObject)customChars.get( new Character( (char) charCurrent ) );
               }
               totalwidth += intObject.width-correctL;
            }
            totalwidth /= -2;
         }
         case ALIGN_LEFT:
         default: {
            d = 1;
            c = correctL;
            break;
         }
      
      }

      GL11.glBindTexture(GL11.GL_TEXTURE_2D, fontTextureID);
      GL11.glBegin(GL11.GL_QUADS);
      
      while (i >= startIndex && i <= endIndex) {
         
         charCurrent = whatchars.charAt(i);
         if (charCurrent < 256) {
            intObject = charArray[charCurrent];
         } else {
            intObject = (IntObject)customChars.get( new Character( (char) charCurrent ) );
         } 
         
         if( intObject != null ) {
            if (d < 0) totalwidth += (intObject.width-c) * d;
               if (charCurrent == '\n') {
                  startY -= fontHeight * d;
                  totalwidth = 0;
                  if (format == ALIGN_CENTER) {
                     for (int l = i+1; l <= endIndex; l++) {
                        charCurrent = whatchars.charAt(l);
                        if (charCurrent == '\n') break;
                        if (charCurrent < 256) {
                           intObject = charArray[charCurrent];
                        } else {
                           intObject = (IntObject)customChars.get( new Character( (char) charCurrent ) );
                        }
                        totalwidth += intObject.width-correctL;
                     }
                     totalwidth /= -2;
                  }
                  //if center get next lines total width/2;
               }
               else {
                  drawQuad((totalwidth + intObject.width) * scaleX + x, startY * scaleY + y,
                     totalwidth * scaleX + x,
                     (startY + intObject.height) * scaleY + y, intObject.storedX + intObject.width,
                     intObject.storedY + intObject.height,intObject.storedX, 
                     intObject.storedY);
                  if (d > 0) totalwidth += (intObject.width-c) * d ;
               }
               i += d;
            
         }
      }
      GL11.glEnd();
   }
   public static int loadImage(BufferedImage bufferedImage) {
       try {
          short width       = (short)bufferedImage.getWidth();
          short height      = (short)bufferedImage.getHeight();
          //textureLoader.bpp = bufferedImage.getColorModel().hasAlpha() ? (byte)32 : (byte)24;
          int bpp = (byte)bufferedImage.getColorModel().getPixelSize();
          ByteBuffer byteBuffer;
          DataBuffer db = bufferedImage.getData().getDataBuffer();
          if (db instanceof DataBufferInt) {
            int intI[] = ((DataBufferInt)(bufferedImage.getData().getDataBuffer())).getData();
            byte newI[] = new byte[intI.length * 4];
            for (int i = 0; i < intI.length; i++) {
               byte b[] = intToByteArray(intI[i]);
               int newIndex = i*4;
               
               newI[newIndex]   = b[1];
               newI[newIndex+1] = b[2];
               newI[newIndex+2] = b[3];
               newI[newIndex+3] = b[0];
            }
            
            byteBuffer  = ByteBuffer.allocateDirect(
                  width*height*(bpp/8))
                                    .order(ByteOrder.nativeOrder())
                                     .put(newI);
          } else {
            byteBuffer  = ByteBuffer.allocateDirect(
                  width*height*(bpp/8))
                                    .order(ByteOrder.nativeOrder())
                                     .put(((DataBufferByte)(bufferedImage.getData().getDataBuffer())).getData());
          }
          byteBuffer.flip();
          
          
          int internalFormat = GL11.GL_RGBA8,
         format = GL11.GL_RGBA;
         IntBuffer   textureId =  BufferUtils.createIntBuffer(1);;
         GL11.glGenTextures(textureId);
         GL11.glBindTexture(GL11.GL_TEXTURE_2D, textureId.get(0));
         

         GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP);
         GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_CLAMP);
         
         GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
         GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
         
         GL11.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_MODULATE);
         
         
         
         GLU.gluBuild2DMipmaps(GL11.GL_TEXTURE_2D,
               internalFormat,
               width,
               height,
               format,
               GL11.GL_UNSIGNED_BYTE,
               byteBuffer);
         return textureId.get(0);
          
      } catch (Exception e) {
         e.printStackTrace();
         System.exit(-1);
       }
      
      return -1;
   }
   public static boolean isSupported(String fontname) {
      Font font[] = getFonts();
      for (int i = font.length-1; i >= 0; i--) {
         if (font[i].getName().equalsIgnoreCase(fontname))
            return true;
      }
      return false;
   }
   public static Font[] getFonts() {
      return GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts();
   }
   public static byte[] intToByteArray(int value) {
        return new byte[] {
                (byte)(value >>> 24),
                (byte)(value >>> 16),
                (byte)(value >>> 8),
                (byte)value};
   }
   
   public void destroy() {
      IntBuffer scratch = BufferUtils.createIntBuffer(1);
      scratch.put(0, fontTextureID);
      GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
      GL11.glDeleteTextures(scratch);
   }
}